﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using RapidWebDev.Common;
using RapidWebDev.Common.Data;
using RapidWebDev.Platform;
using RapidWebDev.SearchIndex.Contract;

namespace RapidWebDev.SearchIndex.MSSQLQueue
{
	/// <summary>
	/// The index service writes indexing messages into MSSQL.
	/// </summary>
	public class IndexApi : IIndexApi
	{
		private static readonly DataContractSerializer dataContractSerializer = new DataContractSerializer(typeof(IndexDocMessage));
		private IApplicationContext applicationContext;
		private IApplicationApi applicationApi;

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="applicationContext"></param>
		/// <param name="applicationApi"></param>
		public IndexApi(IApplicationContext applicationContext, IApplicationApi applicationApi)
		{
			this.applicationContext = applicationContext;
			this.applicationApi = applicationApi;
		}

		/// <summary>
		/// Submit indexing document messages.
		/// </summary>
		/// <param name="indexDocMessages"></param>
		public void SubmitMessages(Collection<IndexDocMessage> indexDocMessages)
		{
			Kit.NotNull(indexDocMessages, "indexDocMessages");

			using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
			using (SearchIndexDataContext ctx = this.CreateDataContext())
			{
				try
				{
					if (ctx.Connection.State != ConnectionState.Open)
						ctx.Connection.Open();

					if (ctx.Transaction == null)
						ctx.Transaction = ctx.Connection.BeginTransaction();

					foreach (IndexDocMessage indexDocMessage in indexDocMessages)
					{
						if (string.IsNullOrEmpty(indexDocMessage.Type))
							throw new ArgumentException("The index document type cannot be null or empty.");

						if (string.IsNullOrEmpty(indexDocMessage.DocumentId))
							throw new ArgumentException("The index document id cannot be null or empty.");

						StringBuilder bodyBuilder = new StringBuilder();
						using (XmlWriter xmlWriter = XmlWriter.Create(bodyBuilder))
						{
							dataContractSerializer.WriteObject(xmlWriter, indexDocMessage);
						};

						ctx.IndexDocuments.InsertOnSubmit(new IndexDocument
						{
							ApplicationId = this.applicationContext.ApplicationId,
							DocumentType = indexDocMessage.Type,
							DocumentId = indexDocMessage.DocumentId,
							OperationType = IndexOperationTypes.Save,
							CreatedOn = DateTime.UtcNow,
							Body = bodyBuilder.ToString(),
							AttachmentCount = indexDocMessage.Attachments != null ? indexDocMessage.Attachments.Count : 0,
							FieldCount = indexDocMessage.Fields != null ? indexDocMessage.Fields.Count : 0,
							Size = bodyBuilder.Length,
							Status = IndexDocumentStatus.Pending
						});
					}

					ctx.SubmitChanges();
					ctx.Transaction.Commit();
				}
				catch (Exception exp)
				{
					Logger.Instance(this).Error(exp);
					if (ctx.Transaction != null) ctx.Transaction.Rollback();
					throw;
				}
			}
		}

		/// <summary>
		/// Delete index documents.
		/// </summary>
		/// <param name="indexDocMessages"></param>
		public void DeleteDocuments(Collection<IndexDocMessage> indexDocMessages)
		{
			Kit.NotNull(indexDocMessages, "indexDocMessages");

			int invalidIndexDocNumber = (from doc in indexDocMessages
										 where string.IsNullOrEmpty(doc.DocumentId) || string.IsNullOrEmpty(doc.Type)
										 select doc).Count();
			if (invalidIndexDocNumber > 0)
				throw new ArgumentException("The deleting document Id and Type cannot be null or empty.", "indexDocMessages");

			using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
			using (SearchIndexDataContext ctx = this.CreateDataContext())
			{
				try
				{
					foreach (IndexDocMessage indexDoc in indexDocMessages)
					{
						IndexDocMessage indexDocMessage = new IndexDocMessage
						{
							Command = "Delete",
							DocumentId = indexDoc.DocumentId,
							Type = indexDoc.Type,
							ApplicationId = this.applicationContext.ApplicationId.ToString()
						};

						StringBuilder bodyBuilder = new StringBuilder();
						using (XmlWriter xmlWriter = XmlWriter.Create(bodyBuilder))
						{
							dataContractSerializer.WriteObject(xmlWriter, indexDocMessage);
						};

						ctx.IndexDocuments.InsertOnSubmit(new IndexDocument
						{
							ApplicationId = this.applicationContext.ApplicationId,
							DocumentType = indexDoc.Type,
							DocumentId = indexDoc.DocumentId,
							OperationType = IndexOperationTypes.Delete,
							CreatedOn = DateTime.UtcNow,
							Body = bodyBuilder.ToString(),
							AttachmentCount = 0,
							FieldCount = 0,
							Size = bodyBuilder.Length,
							Status = IndexDocumentStatus.Pending
						});
					}

					ctx.SubmitChanges();
				}
				catch (Exception exp)
				{
					Logger.Instance(this).Error(exp);
					if (ctx.Transaction != null) ctx.Transaction.Rollback();
					throw;
				}
			}
		}

		/// <summary>
		/// Delete all documents in the index directory.
		/// </summary>
		public void DeleteAllDocuments()
		{
			using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
			using (SearchIndexDataContext ctx = this.CreateDataContext())
			{
				try
				{
					if (ctx.Connection.State != ConnectionState.Open)
						ctx.Connection.Open();

					if (ctx.Transaction == null)
						ctx.Transaction = ctx.Connection.BeginTransaction();

					string command = string.Format(CultureInfo.InvariantCulture, "DELETE FROM {0} WHERE ApplicationId='{1}'",
						ctx.Mapping.GetTable(typeof(IndexDocument)).TableName,
						this.applicationContext.ApplicationId);

					ctx.ExecuteCommand(command);

					IndexDocMessage indexDocMessage = new IndexDocMessage
					{
						Command = "DeleteAll",
						DocumentId = "Random:" + Guid.NewGuid().ToString(),
						ApplicationId = this.applicationContext.ApplicationId.ToString()
					};

					StringBuilder bodyBuilder = new StringBuilder();
					using (XmlWriter xmlWriter = XmlWriter.Create(bodyBuilder))
					{
						dataContractSerializer.WriteObject(xmlWriter, indexDocMessage);
					};

					ctx.IndexDocuments.InsertOnSubmit(new IndexDocument
					{
						ApplicationId = this.applicationContext.ApplicationId,
						DocumentType = "DeleteAll",
						DocumentId = indexDocMessage.DocumentId,
						OperationType = IndexOperationTypes.DeleteAll,
						CreatedOn = DateTime.UtcNow,
						Body = bodyBuilder.ToString(),
						AttachmentCount = 0,
						FieldCount = 0,
						Size = bodyBuilder.Length,
						Status = IndexDocumentStatus.Pending
					});

					ctx.SubmitChanges();
					ctx.Transaction.Commit();
					ctx.Connection.Close();
				}
				catch (Exception exp)
				{
					Logger.Instance(this).Error(exp);
					if (ctx.Transaction != null) ctx.Transaction.Rollback();
					throw;
				}
			}
		}

		/// <summary>
		/// Optimize the index directory.
		/// </summary>
		public void Optimize()
		{
			using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Suppress))
			using (SearchIndexDataContext ctx = this.CreateDataContext())
			{
				try
				{
					IndexDocMessage indexDocMessage = new IndexDocMessage
					{
						Command = "Optimize",
						DocumentId = "Random:" + Guid.NewGuid().ToString(),
						ApplicationId = this.applicationContext.ApplicationId.ToString()
					};

					StringBuilder bodyBuilder = new StringBuilder();
					using (XmlWriter xmlWriter = XmlWriter.Create(bodyBuilder))
					{
						dataContractSerializer.WriteObject(xmlWriter, indexDocMessage);
					};

					ctx.IndexDocuments.InsertOnSubmit(new IndexDocument
					{
						ApplicationId = this.applicationContext.ApplicationId,
						DocumentType = "Optimize",
						DocumentId = indexDocMessage.DocumentId,
						OperationType = IndexOperationTypes.DeleteAll,
						CreatedOn = DateTime.UtcNow,
						Body = bodyBuilder.ToString(),
						AttachmentCount = 0,
						FieldCount = 0,
						Size = bodyBuilder.Length,
						Status = IndexDocumentStatus.Pending
					});

					ctx.SubmitChanges();
				}
				catch (Exception exp)
				{
					Logger.Instance(this).Error(exp);
					throw;
				}
			}
		}

		private SearchIndexDataContext CreateDataContext()
		{
			ApplicationObject applicationObject = this.applicationApi.Get(this.applicationContext.ApplicationId);
			if (applicationObject == null)
				return DataContextFactory.Create<SearchIndexDataContext>();

			string connectionName = applicationObject["IndexQueueDbConnectionName"] as string;
			if (string.IsNullOrEmpty(connectionName))
				return DataContextFactory.Create<SearchIndexDataContext>();

			ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionName];
			if (connectionStringSettings == null)
				return DataContextFactory.Create<SearchIndexDataContext>();

			return new SearchIndexDataContext(connectionStringSettings.ConnectionString);
		}
	}
}